package edu.iscas.appcheckclient.service;

import android.accessibilityservice.AccessibilityService;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Rect;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.EditText;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import edu.iscas.appcheckclient.Configuration;
import edu.iscas.appcheckclient.R;
import edu.iscas.appcheckclient.activity.AppListActivity;
import edu.iscas.appcheckclient.broadcastreceiver.HookedContentReceiver;
import edu.iscas.appcheckclient.model.AppInfo;
import edu.iscas.appcheckclient.model.Selector;
import edu.iscas.appcheckclient.model.actions.Action;
import edu.iscas.appcheckclient.model.actions.MockedHttpResponse;
import edu.iscas.appcheckclient.model.actions.MockedIntent;
import edu.iscas.appcheckclient.model.actions.MockedLocation;
import edu.iscas.appcheckclient.model.actions.ParamAction;
import edu.iscas.appcheckclient.model.actions.Perform;
import edu.iscas.appcheckclient.model.actions.Press;
import edu.iscas.appcheckclient.model.actions.Request;
import edu.iscas.appcheckclient.model.actions.Screenshot;
import edu.iscas.appcheckclient.service.check.CheckMenuListener;
import edu.iscas.appcheckclient.service.network.RequestManager;

public class RecordService extends AccessibilityService {

    private static final String TAG = RecordService.class.getSimpleName();

    private static RecordService recordService;
    public static RequestManager requestManager;
    public static SpinnerNode spinnerNode;
    private static String selfPackageName;
    private static String currentActivityName;
    private static boolean isRecording;
    private WindowIdManager windowIdManager;

    private static List<Action> actionList;
    private static List<Action> mockedHttpResponseList;
    private static List<Action> mockedLocationList;

    private static Stack<Integer> windowIdStack;

    private static List<ParamAction> paramActionList;

    private static AppInfo testAppInfo;
    private static BroadcastReceiver hookedContentReceiver;

    private static Context context;
    private String textChangedClassName;

    public static final String START_RECORD = "edu.iscas.appcheckclient.broadcastreceiver.START_RECORD";
    public static final String STOP_RECORD = "edu.iscas.appcheckclient.broadcastreceiver.STOP_RECORD";

    public static RecordService getRecordService() {
        return recordService;
    }

    public static AppInfo getTestAppInfo() {
        return testAppInfo;
    }

    public static boolean isRecording(){
        return isRecording;
    }

    public static List<Action> getActionList() {
        return actionList;
    }

    public static List<Action> getMockedHttpResponseList() {
        return mockedHttpResponseList;
    }

    public static List<Action> getMockedLocationList() {
        return mockedLocationList;
    }

    /**
     * resume recording
     */
    public static void resumeRecording(){
        isRecording = true;
        //显示悬浮窗
        Intent serviceIntent = new Intent(recordService.getApplicationContext(), FloatingWindowService.class);
        recordService.getApplicationContext().startService(serviceIntent);
    }

    /**
     * pause recording
     */
    public static void pauseRecording(){
        isRecording = false;
        if(recordService.getApplicationContext() == null){
            Log.d(TAG, "Null context");
        }
        else{
            //关闭悬浮窗
            recordService.getApplicationContext().stopService(new Intent(recordService.getApplicationContext(), FloatingWindowService.class));
        }
    }

    /**
     * do init the recording service and call {@link RecordService#resumeRecording()}  at last
     */
    public static void startRecording(AppInfo appInfo){
        clean();
        RecordService.testAppInfo = appInfo;

        //register the HookedContentReceiver
        IntentFilter filter = new IntentFilter();
        filter.addAction(HookedContentReceiver.HOOKED_HTTP_MESSAGE_RECEIVER);
        filter.addAction(HookedContentReceiver.HOOKED_INTENT_RECEIVER);
        filter.addAction(HookedContentReceiver.HOOKED_LOCATION_RECEIVER);
        hookedContentReceiver = new HookedContentReceiver(recordService);
        context.registerReceiver(hookedContentReceiver, filter);

        //send message to instrumented app
        context.sendStickyBroadcast(new Intent(START_RECORD));
        Log.d(TAG, "send start record message");
        resumeRecording();
    }

    /**
     * call {@link RecordService#pauseRecording()} at first and do the clean job.
     */
    public static void stopRecording(){
        pauseRecording();
        clean();
        // unregister the broadCastReceiver
        if(hookedContentReceiver != null){
            context.unregisterReceiver(hookedContentReceiver);
            hookedContentReceiver = null;
        }
        // send message to the instrumented app
        context.sendBroadcast(new Intent(STOP_RECORD));
        context.removeStickyBroadcast(new Intent(START_RECORD));
        Log.d(TAG, "send stop record message");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // must unregister the broadcastreceivers and send stop recording broadcast
        stopRecording();
    }

    /**
     * clean the records and the relative fields, prepares to the next recording
     */
    public static void clean(){
        actionList = new ArrayList<>();
        mockedHttpResponseList = new ArrayList<>();
        mockedLocationList = new ArrayList<>();
        paramActionList = new ArrayList<>();
        windowIdStack = new Stack<>();
        currentActivityName = null;
        spinnerNode = new SpinnerNode();
    }

    public static Request createRequest(){
        return new Request(
                actionList,
                mockedHttpResponseList,
                mockedLocationList,
                testAppInfo.getAppName(),
                "name_" + String.valueOf(System.currentTimeMillis()),
                testAppInfo.getPackageName(),
                false,
                currentActivityName,
                "test description"
        );
    }

    private boolean checkSystemEvent(AccessibilityNodeInfo accessibilityNodeInfo){
        return TextUtils.equals("com.android.systemui", accessibilityNodeInfo.getPackageName().toString());
    }

    /**
     * breadth first search from the root to the child looking for the child node contains the given point<br>
     * TODO: use Depth First Search instead
     * @param rootNodeInfo the root node
     * @param x the x position of the given point
     * @param y the y position of the given point
     * @return the node contains the given point
     */
    private AccessibilityNodeInfo locateNodeInfoByAxis(AccessibilityNodeInfo rootNodeInfo, float x, float y){
        if(rootNodeInfo == null){
            return null;
        }
        AccessibilityNodeInfo targetNodeInfo = null;
        //队列
        ArrayList<AccessibilityNodeInfo> queue = new ArrayList<>();
        queue.add(rootNodeInfo);
        Rect targetRect = new Rect((int)Math.floor(x), (int)Math.floor(y), (int)Math.ceil(x), (int)Math.ceil(y));
        while(!queue.isEmpty()){
            AccessibilityNodeInfo nodeInfo = queue.remove(0);
            Rect nodeInfoRect = new Rect();
            nodeInfo.getBoundsInScreen(nodeInfoRect);
            if(nodeInfoRect.contains(targetRect)){
                for(int i = 0; i < nodeInfo.getChildCount(); i++){
                    AccessibilityNodeInfo childNodeInfo = nodeInfo.getChild(i);
                    if(childNodeInfo == null)
                        Log.d(TAG, "getNodeInfoByAxis: Child node not found");
                    else
                        queue.add(childNodeInfo);
                }
                targetNodeInfo = nodeInfo;
            }
        }
        return targetNodeInfo;
    }

    public AccessibilityNodeInfo locateNodeInfoByAxis(float x, float y){
        return locateNodeInfoByAxis(getRootInActiveWindow(), x, y);
    }

    public View.OnClickListener getCheckMenuListener(AccessibilityNodeInfo nodeInfo, View view){
        return new CheckMenuListener(nodeInfo, createSelector(nodeInfo), actionList, view, paramActionList);
    }

    private void handleSpinnerNode(int type){
        if(spinnerNode.getAccessibilityNodeInfo() == null) return;
        switch (spinnerNode.state){
            case 2:
                //Represents the event of selecting an item usually in the context of an android.widget.AdapterView.
                if(type == AccessibilityEvent.TYPE_VIEW_SELECTED) return;
                if(spinnerNode.isE()) return;
                spinnerNode.setState(-1);
                spinnerNode.setAccessibilityNodeInfo(null);
                return;
            case 3:
                if(spinnerNode.isE() || type == AccessibilityEvent.TYPE_VIEW_SELECTED){
                    if(spinnerNode.isF() || type == AccessibilityEvent.TYPE_VIEW_SELECTED)return;
                    spinnerNode.setState(-1);
                    spinnerNode.setAccessibilityNodeInfo(null);
                    return;
                }
                actionList.add(new Press(Press.Type.back));
                Log.d("a", "Espresso.pressBack()");
                spinnerNode.setE(false);
                spinnerNode.setState(-1);
                spinnerNode.setAccessibilityNodeInfo(null);
                return;
            case 4:
                spinnerNode.setE(false);
                spinnerNode.setState(-1);
                spinnerNode.setAccessibilityNodeInfo(null);
        }

    }

    public static boolean isChildOfLastParamAction(AccessibilityNodeInfo nodeInfo){
        //如果xpath包含保存的paramActionList里面的最后一个nodeInfo的xpath
        return (paramActionList.size() > 0) && (computeXPath(nodeInfo).contains(
                (paramActionList.get(paramActionList.size() - 1)).getStringParam("xpath"))
        );
    }

    private boolean isSameScrollAction(AccessibilityNodeInfo nodeInfo){
        if((paramActionList != null) && (paramActionList.size() > 0)){//如果paramActionList中有元素
            Action action = paramActionList.get(paramActionList.size() - 1);//取最后一个元素
            if(action instanceof Perform){
                //如果保存的paramAction是ScrollAdapterView类型而且selector和参数相同
                if((((Perform) action).getType().equals(Perform.Type.ScrollAdapterView))
                        && (((Perform) action).getSelector().equals(createSelector(nodeInfo)))){
                    return true;
                }
            }
        }
        return false;
    }

    public static String convertToUTF8(String str){
        try{
            str = Base64.encodeToString(str.getBytes("UTF-8"), 0);
            return str;
        }catch(Exception e){
            Log.e(TAG, "Failed to encode base 64.", e);
            e.printStackTrace();
        }
        return "";
    }

    private boolean isSubclass(String class1, String class2) {
        try {
            return Class.forName(class2).isAssignableFrom(Class.forName(class1));
        }catch (ClassNotFoundException e){
            Log.e(TAG, "isSubclass: ClassNotFoundException");
        }
        return false;
    }

    private String getBaseClass(String str){
        if(isSubclass(str, this.getResources().getString(R.string.abs_list_view))){
            return this.getResources().getString(R.string.abs_list_view);
        }
        if(isSubclass(str, this.getResources().getString(R.string.abs_spinner))){
            return this.getResources().getString(R.string.abs_spinner);
        }
        if(isSubclass(str, this.getResources().getString(R.string.adapter_view_animator))){
            return this.getResources().getString(R.string.adapter_view_animator);
        }
        return this.getResources().getString(R.string.adapter_view);
    }


    /**
     * Create the XPath of the node from child(target) to parent
     * @param nodeInfo the target node info
     * @return the XPath of the node info
     */
    private static String computeXPath(AccessibilityNodeInfo nodeInfo){
        AccessibilityNodeInfo parentNodeInfo;
        //路径
        String path = "";
        int i;
        while(nodeInfo != null){
            String className = nodeInfo.getClassName().toString();
            parentNodeInfo = nodeInfo.getParent();
            if(parentNodeInfo != null){
                if(parentNodeInfo.getChildCount() > 1){
                    ArrayList children = new ArrayList();
                    i = 0;
                    //遍历父节点的子节点
                    while(i < parentNodeInfo.getChildCount()){
                        AccessibilityNodeInfo childNodeInfo = parentNodeInfo.getChild(i);
                        //如果相同
                        if((childNodeInfo != null) && (TextUtils.equals(childNodeInfo.getClassName().toString(), className)) && childNodeInfo.isVisibleToUser()) {
                            children.add(childNodeInfo);
                        }
                        i += 1;
                    }
                    //如果这个子节点唯一（可与其他的区分开）
                    if(children.size() == 1){
                        path = "/" + className + path;
                    }else{//如果不能区分子节点
                        int j = 0;
                        while(children.size() >= j){
                            AccessibilityNodeInfo tNodeInfo = (AccessibilityNodeInfo)children.get(j);
                            if(tNodeInfo.equals(nodeInfo)){//找到参数节点，注意这里是equals也就是唯一的
                                path = "/" + className + "[" + (++j) + "]" + path;
                                break;
                            }else{
                                j ++;
                            }
                        }
                        if(j > children.size()){//如果到最后没有找到
                            Log.e(TAG, "computeXPath: child not found from parent");
                            return "";
                        }
                    }
                }else{
                    path = "/" + className + path;
                }
                nodeInfo = parentNodeInfo;
            }else{//父节点是null
                path = "/" + className + path;
                nodeInfo = null;
            }
        }
        return path;
    }

    public void handleMockedIntent(MockedIntent mockedIntent){
        Log.d(TAG, "STUB_INTENT");
        actionList.add(mockedIntent);
    }

    public void handleMockedLocation(MockedLocation mockedLocation){
        Log.d(TAG, "STUB_LOCATION");
        mockedLocationList.add(mockedLocation);
    }

    public void handleMockedHttpResponse(MockedHttpResponse mockedHttpResponse){
        Log.d(TAG, "STUB_HTTP_RESPONSE");
        mockedHttpResponseList.add(mockedHttpResponse);
    }

    private void handleTypeViewClicked(AccessibilityEvent event){
        Log.d(TAG, "TYPE_VIEW_CLICKED");
        AccessibilityNodeInfo nodeInfo = event.getSource();
        if(nodeInfo != null){
            //如果点击的是spinner，在SpinnerNode中记录下来
            if(nodeInfo.getClassName().toString().equals(this.getResources().getString(R.string.spinner))){
                spinnerNode.setAccessibilityNodeInfo(nodeInfo);
                spinnerNode.setSelector(createSelector(nodeInfo));
                spinnerNode.setAccessibilityEvent(event);
                spinnerNode.setState(0);
                return;
            }
            //如果SpinnerNode里面保存了node,直接跳过
            if(spinnerNode.getAccessibilityNodeInfo() != null)
                return;
            //点的控件是最后一个保存的子控件，
            //将paramActionList中的最后一个nodeInfo添加到actionList中
            if(isChildOfLastParamAction(nodeInfo)){
                actionList.add(paramActionList.get(paramActionList.size() - 1));
            }
            Selector selector = createSelector(nodeInfo);
            //Log.d(TAG, "ResourceID : " + selector);
            //把参数的Action加到actionList中
            Perform perform = new Perform(Perform.Type.Click, selector, event.getEventTime());
            actionList.add(perform);
            Log.d(TAG, perform.brewStatement(true));
            //清空paramActionList
            paramActionList.clear();
        }else{
            Log.d(TAG, "hadleTypeViewClicked: node is null");
        }
    }

    private void handleTypeViewLongClicked(AccessibilityEvent event){
        Log.d(TAG, "TYPE_VIEW_LONG_CLICKED");
        AccessibilityNodeInfo nodeInfo = event.getSource();
        if(nodeInfo != null){
            if(isChildOfLastParamAction(nodeInfo)){
                actionList.add(paramActionList.get(paramActionList.size() - 1));
            }
            Perform perform = new Perform(Perform.Type.LongClick,
                    createSelector(nodeInfo),
                    event.getEventTime());
            actionList.add(perform);
            Log.d(TAG, perform.brewStatement(true));
            paramActionList.clear();
            return;
        }
        Log.d(TAG, "handleTypeViewLongClicked: node is null");

    }

    private void handleTypeTextChanged(AccessibilityEvent event){
        Log.d(TAG, "TYPE_TEXT_CHANGED");
        AccessibilityNodeInfo nodeInfo = event.getSource();
        String str;
        if(nodeInfo != null){
            if(isChildOfLastParamAction(nodeInfo)){
                actionList.add(paramActionList.get(paramActionList.size() - 1));
            }
            Selector selector = createSelector(nodeInfo);
            this.textChangedClassName = selector.getClassName();
            if(nodeInfo.getText() == null){
                if(nodeInfo.getContentDescription() == null){
                    str = "";
                }else{
                    str = nodeInfo.getContentDescription().toString();
                }
            }else{
                str = nodeInfo.getText().toString();
            }
            Perform perform = new Perform(Perform.Type.Type, selector, event.getEventTime());
            perform.addParam("text", str);
            perform.addParam("is_password", nodeInfo.isPassword());
            actionList.add(perform);
            Log.d(TAG, perform.brewStatement(true));
            paramActionList.clear();
            return;
            //this.o = this.n;
        }
        Log.d(TAG, "handleTypeTextChanged: node is null");
    }

    private void handleWindowContentChanged(AccessibilityEvent event){
        Log.d(TAG, "TYPE_WINDOW_CONTENT_CHANGED");
        this.windowIdManager.insertUpdateWindow();
    }
    private void handleWindowsChanged(AccessibilityEvent event) {
        Log.d(TAG, "TYPE_WINDOWS_CHANGED");
        event.getSource();
    }

    private void handleWindowStateChanged(AccessibilityEvent accessibilityEvent){
        Log.d(TAG, "TYPE_WINDOW_STATE_CHANGED");
        Log.d(TAG, "Window Id is " + accessibilityEvent.getWindowId());

        if((accessibilityEvent.getClassName() != null) && (currentActivityName == null)){
            currentActivityName = accessibilityEvent.getClassName().toString();
        }
        //如果是按了Spinner的状态，更新状态
        if(spinnerNode.getAccessibilityNodeInfo() != null){
            spinnerNode.updateState();
        }
        this.windowIdManager.insertUpdateWindow();
        paramActionList.clear();
    }

    private void handleTypeViewSelected(AccessibilityEvent event){
        Log.d(TAG, "TYPE_WINDOW_SELECTED");
        this.windowIdManager.insertUpdateWindow();
        AccessibilityNodeInfo nodeInfo = event.getSource();
        if(nodeInfo != null){
            if(spinnerNode.getAccessibilityNodeInfo() != null){
                spinnerNode.updateState();
                if(spinnerNode.isE()){
                    Selector selector = spinnerNode.getSelector(spinnerNode.getAccessibilityNodeInfo());
                    Perform perform = new Perform(Perform.Type.TypeViewSelected, selector, event.getEventTime());
                    perform.addParam("position", event.getFromIndex());
                    perform.addParam("text", event.getText().toString().replace("[", "").replace("]", ""));
                    perform.addParam("classType", getBaseClass(nodeInfo.getClassName().toString()));
                    actionList.add(perform);
                    Log.d(TAG, perform.brewStatement(true));
                    spinnerNode.setF(true);
                }else{
                    Selector clickSelector = createSelector(nodeInfo);
                    actionList.add(new Perform(Perform.Type.Click, clickSelector, event.getEventTime()));
                    Selector selectSelector = spinnerNode.getSelector(nodeInfo);
                    Perform selectPerform = new Perform(Perform.Type.TypeViewSelected, selectSelector, event.getEventTime());
                    selectPerform.addParam("position", event.getFromIndex());
                    selectPerform.addParam("text", event.getText().toString().replace("[", "").replace("]", ""));
                    selectPerform.addParam("classType", getBaseClass(nodeInfo.getClassName().toString()));
                    actionList.add(selectPerform);
                    Log.d(TAG, selectPerform.brewStatement(true));
                    spinnerNode.setF(true);
                    Log.d(TAG, "SetF true");
                }
            }
            paramActionList.clear();
            return;
        }
        Log.d(TAG, "handleTypeViewSelected: node is null");
    }

    private void handleScrollAction(AccessibilityEvent event){
        Log.d(TAG, "TYPE_VIEW_SCROLL");
        AccessibilityNodeInfo nodeInfo = event.getSource();

        if(!nodeInfo.isVisibleToUser())return;

        if(isSameScrollAction(nodeInfo)){
            //移除最后一个paramAction
            paramActionList.remove(paramActionList.size() - 1);
        }
        //如果保存了node，保存对应的selector
        if(spinnerNode != null && spinnerNode.getAccessibilityNodeInfo() != null){
            spinnerNode.saveSelector(spinnerNode.getAccessibilityNodeInfo(), createSelector(nodeInfo));
            Log.d(TAG, "save spinnerNode selector");
        }else{//没有保存node
            Selector selector = createSelector(nodeInfo);

            Log.d(TAG + "#handleScrollAction", "From index " + event.getFromIndex() + " To index" + event.getToIndex());

            //如果event不是Spinner的事件，并且能获取到index说明是scroll事件，且selector的value为空
            if(!(event.getClassName().toString().equals(this.getResources().getString(R.string.spinner))) &&
                    (event.getFromIndex() != -1) && (event.getToIndex() != -1) /*&&
                    selector.getValue().equals("")*/){
                //将scroll事件保存到paramActionList
                Perform perform = new Perform(Perform.Type.ScrollAdapterView, selector, event.getEventTime());
                perform.addParam("position", event.getFromIndex());
                perform.addParam("xpath", computeXPath(nodeInfo));
                perform.addParam("classType", getBaseClass(nodeInfo.getClassName().toString()));
                Log.d(TAG, perform.brewStatement(true));
                paramActionList.add(perform);
            }
            Log.d(TAG, selector.brewStatement(true));
        }
    }

    public static Selector createSelector(RecordService recordService, AccessibilityNodeInfo nodeInfo){
        return recordService.createSelector(nodeInfo);
    }

    private Selector createSelector(AccessibilityNodeInfo nodeInfo){
        String resourceName = nodeInfo.getViewIdResourceName();
        Integer idCount = this.windowIdManager.getResourceNameCount(nodeInfo.getWindowId(), resourceName);
        if((resourceName != null) && idCount == 1){//唯一windowId
            //resourceName = ResourceNameReplacer.replace(resourceName);

            String str = resourceName;
            if(resourceName.startsWith(testAppInfo.getPackageName() + ".")){//如果是目标包的控件
                str = resourceName.replaceAll(testAppInfo.getPackageName() + ".", "");//把包名去了
            }

            if(nodeInfo.getClassName() != null){
                return new Selector(Selector.Type.ResourceID, nodeInfo.getClassName().toString(), str);
            }else{
                return new Selector(Selector.Type.ResourceID, "", str);
            }
        }
        if(canMatchByContentDescription(nodeInfo)){
            if(nodeInfo.getClassName() != null)
                return new Selector(Selector.Type.ContentDesc, nodeInfo.getClassName() + "", nodeInfo.getContentDescription().toString());
            return new Selector(Selector.Type.ContentDesc, "", nodeInfo.getContentDescription().toString());
        }
        if(canMatchByHint(nodeInfo)){
            if (nodeInfo.getClassName() != null) {
                return new Selector(Selector.Type.Hint, nodeInfo.getClassName() + "", nodeInfo.getText().toString());
            }
            return new Selector(Selector.Type.Hint, "", nodeInfo.getText().toString());
        }
        if(canMatchByText(nodeInfo)){
            if (nodeInfo.getClassName() != null) {
                return new Selector(Selector.Type.Text, nodeInfo.getClassName() + "", nodeInfo.getText().toString());
            }
            return new Selector(Selector.Type.Text, "", nodeInfo.getText().toString());
        }
        //计算xpath
        String xpath = computeXPath(nodeInfo);
        if(nodeInfo.getClassName() != null){
            return new Selector(Selector.Type.XPath, nodeInfo.getClassName().toString(), xpath);
        }
        return new Selector(Selector.Type.XPath, "", xpath);
    }

    private boolean canMatchByContentDescription(AccessibilityNodeInfo nodeInfo){
        return(nodeInfo.getContentDescription() != null) &&
                (this.windowIdManager.canMatchByContentDescription(nodeInfo.getWindowId(), nodeInfo.getContentDescription().toString())) &&
                (nodeInfo.getContentDescription().length() < 250);
    }

    private boolean canMatchByHint(AccessibilityNodeInfo nodeInfo){
        if(canGetHint(nodeInfo)){
            String text = nodeInfo.getText().toString();
            if(this.windowIdManager.canMatchByHint(nodeInfo.getWindowId(), text)){
                return true;
            }
        }
        return false;
    }

    private boolean canMatchByText(AccessibilityNodeInfo nodeInfo){
        return (nodeInfo.getText() != null) &&
                (this.windowIdManager.canMatchByText(nodeInfo.getWindowId(), nodeInfo.getText().toString())) &&
                (nodeInfo.getText().length() < 250);
    }

    public boolean canGetHint(AccessibilityNodeInfo nodeInfo){
        if(nodeInfo.getClassName() != null){
            String className = nodeInfo.getClassName().toString();
            try {
                //如果是EditText的子类
                if((EditText.class.isAssignableFrom(Class.forName(className))) &&
                        (nodeInfo.getTextSelectionEnd() == -1) &&
                        (nodeInfo.getTextSelectionStart() == -1)){
                    if(nodeInfo.getText() != null)
                        return true;
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    public void addScreenshotAction(){
        actionList.add(new Screenshot(Screenshot.Type.Screenshot,
                Configuration.sharedPreferences.getString(
                        getResources().getString(R.string.config_key_screenshot_filename),
                        getResources().getString(R.string.config_default_value_screenshot_filename)),
                Configuration.sharedPreferences.getBoolean(
                        getResources().getString(R.string.config_key_add_timestamp)
                        , true)
        ));
    }

    static boolean backKeyFlag = false;
    @Override
    protected boolean onKeyEvent(KeyEvent event) {
        if(isRecording()){
            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
                // 按下BACK，同时没有重复
                if(event.getAction() == KeyEvent.ACTION_DOWN){
                    Log.d(TAG, "BackKey Down");
                    backKeyFlag = true;
                }else if(event.getAction() == KeyEvent.ACTION_UP && backKeyFlag){
                    Log.d(TAG, "BackKey Up");
                    backKeyFlag = false;
                    actionList.add(new Press(Press.Type.back));
                }
            }
        }
        return super.onKeyEvent(event);
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        AccessibilityNodeInfo eventNodeInfo = event.getSource();
        //this.n += 1;

        if(!isRecording())
            return;

        Log.d(TAG, "=========================start==========================");

        if(eventNodeInfo == null ||checkSystemEvent(eventNodeInfo)) {
            Log.d(TAG, "eventNodeInfo not qualified");
        }else{
            String str = eventNodeInfo.getPackageName().toString();
            // for the event on this package and inputmethod
            if((TextUtils.equals(selfPackageName, str)) || TextUtils.indexOf(str, this.getResources().getString(R.string.input_method)) == 0){
                return;
            }
            // for the event on the test package
            if(TextUtils.equals(str, testAppInfo.getPackageName())){
                handleSpinnerNode(eventType);
                switch (eventType){
                    case AccessibilityEvent.TYPE_VIEW_CLICKED:
                        //Represents the event of clicking on a View like Button, CompoundButton, etc.
                        handleTypeViewClicked(event);
                        Log.d(TAG, "spinnerNode.state = " + spinnerNode.state);
                        return;
                    case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED:
                        //Represents the event of long clicking on a View like Button, CompoundButton, etc.
                        handleTypeViewLongClicked(event);
                        return;
                    case AccessibilityEvent.TYPE_VIEW_SELECTED:
                        ///Represents the event of selecting an item usually in the context of an AdapterView.
                        handleTypeViewSelected(event);
                        Log.d(TAG, "spinnerNode.state = " + spinnerNode.state);
                        return;
                    case AccessibilityEvent.TYPE_VIEW_FOCUSED:
                        Log.d(TAG, "TYPE_VIEW_FOCUSED");
                        return;
                    case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
                        //Represents the event of changing the text of an EditText.
                        handleTypeTextChanged(event);
                        return;
                    case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
                        //Represents the event change in the windows shown on the screen.
                        handleWindowsChanged(event);
                        return;
                    case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                        //Represents the event of opening a PopupWindow, Menu, Dialog, etc.
                        handleWindowStateChanged(event);
                        Log.d(TAG, "spinnerNode.state = " + spinnerNode.state);
                        return;
                    case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
                        //Represents the event of changing the content of a window and more specifically the sub-tree rooted at the event's source.
                        handleWindowContentChanged(event);
                        return;
                    case AccessibilityEvent.TYPE_VIEW_SCROLLED:
                        //Represents the event of scrolling a view.
                        handleScrollAction(event);
                        Log.d(TAG, "spinnerNode.state = " + spinnerNode.state);
                        return;
                    case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:
                        //Represents the event of changing the selection in an EditText.
                        Log.d(TAG, "TYPE_VIEW_TEXT_SELECTION_CHANGED");
                        //this.n -= 1;
                        return;
                    case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
                        //Represents the event of clearing accessibility focus.
                        return;
                }
                Log.d("onAccessibilityEvent", "eventType = " + eventType);
            }
        }
    }


    @Override
    public void onInterrupt() {
        Log.d(TAG, "RecordService Interrupted");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
        context = this;
        recordService = this;
        this.windowIdManager = new WindowIdManager(this);

        stopRecording();
        Configuration.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
    }

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        Log.d(TAG, "onServiceConnected");

    }
}
